/*
 * Copyright 2010 Michael Nitsche
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package de.mnit.basis.daten;

import java.awt.Point;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.NumberFormat;

import de.mnit.basis.fehler.Fehler;



/**
 * @author Michael Nitsche
 * @license LGPL
 */
public class Mathe {

	// ZAHLENFOLGE

	public static <T extends Comparable<? super T>> T min(T... zahlen) {
		Fehler.bereich.wenn_ZuKurz(1, zahlen);
		T erg = zahlen[0];
		for(T zahl : zahlen)
			if(zahl.compareTo(erg)<0)
				erg=zahl;
		return erg;
	}

	public static <T extends Comparable<? super T>> T max(T... zahlen) {
		Fehler.bereich.wenn_ZuKurz(1, zahlen);
		T erg = zahlen[0];
		for(T zahl : zahlen)
			if(zahl.compareTo(erg)>0)
				erg=zahl;
		return erg;
	}

	/**
	 * Elemente in zahlen dürfen auch null sein!
	 */
	public static <T extends Comparable<? super T>> T minNull(T... zahlen) {
		Fehler.bereich.wenn_ZuKurz(1, zahlen);
		T erg = null;
		for(T zahl : zahlen) {
			if(zahl==null)
				continue;
			if(erg==null)
				erg=zahl;
			else if(zahl.compareTo(erg)<0)
				erg=zahl;
		}
		return erg;
	}

	public static <T extends Number> T groessteAbweichung(T... zahlen) {
		Fehler.bereich.wenn_ZuKurz(1, zahlen);
		double mitte=0;
		for(T zahl : zahlen) mitte+=zahl.doubleValue()/zahlen.length;
		T erg = zahlen[0];
		double abw=0;
		for(T zahl : zahlen)
			if(Mathe.absBetrag(mitte-zahl.doubleValue())>abw || abw==0) {
				abw=Mathe.absBetrag(mitte-zahl.doubleValue());
				erg=zahl;
			}
		return erg;
	}

	public static <T extends Number> double durchschnitt(T... zahlen) {
		Fehler.bereich.wenn_ZuKurz(1,zahlen);
		double schnitt=0d;
		for(T zahl : zahlen) schnitt+=zahl.doubleValue()/zahlen.length;
		return schnitt;
	}

	// RUNDEN

	/**
	 * Rount-to-EVEN ist mathematisch korrekt
	 * HALF_UP ist kaufmännisch
	 */
	public static String rundenZuText(double zahl, int stellen, RoundingMode rm) {
		if(rm!=null && rm== RoundingMode.HALF_UP)
			return rundenZuText(zahl, stellen);
		else
			throw Fehler.da_ToDo();
	}

	/**
	 * Mit kaufmännischer Rundung!!!
	 * TODO Evtl. hier zwei Methoden: EVEN und HALF_UP!
	 */
	public static String rundenZuText(double zahl, int stellen) {
//		return rundenZuText(zahl, stellen, RoundingMode.HALF_UP);
		zahl=normalisiere(zahl, stellen+1);
		String format=
//			(zahl<0 ? "-" : "") //- wird automatisch gesetzt!
			"0" // # würde hier u.U. zu ",123" führen
			+(stellen>0 ? "." : "")
			+FormatText.folge('0', stellen);
		DecimalFormat df = new DecimalFormat(format);

		zahl=runden(zahl, stellen);
		//Das ist die einfache Variante für Java6
//		if(rm!=null)
//			df.setRoundingMode(rm);
		return df.format(zahl);
	}

	/**
	 * Mit kaufmännischer Rundung!!!
	 * Hier auf alle Fälle!
	 */
	public static String rundenZuWaehrung(double zahl) {
		zahl=normalisiere(zahl, 2);
		NumberFormat nf= NumberFormat.getCurrencyInstance();
//		nf.setRoundingMode(RoundingMode.HALF_UP); // Java6
		zahl=runden(zahl, 2);
		return nf.format(zahl);
	}

	public static String aufrundenZuText(double zahl, int stellen) {
		return rundenZuText(zahl, stellen, RoundingMode.UP);
	}

	public static int aufrunden(double zahl) {
		int erg=(int)zahl;
		if(zahl>erg) erg++;
		return erg;
	}

	public static double aufrunden(double zahl, int stellen) {
		Fehler.zahl.wenn_ZuKlein(0, stellen);
		int faktor = (int)Math.pow(10, stellen);
		int erg=(int)(zahl*faktor);
		if(zahl*faktor>erg) erg++;
		return ((double)erg)/faktor;
	}

	public static int abrunden(double zahl) {
		return (int)zahl;
	}

	public static double runden(double zahl, int stellen) {
		Fehler.zahl.wenn_Ausserhalb(0, 10, stellen);
		double multi= stellen==0 ? 1 : Mathe.potenz(10, stellen);
		return Math.round(zahl*multi) / multi;
	}

	/**@deprecated  >> runden2Integer **/
	public static Integer rundenZuInt(Double zahl) {
		if(zahl==null) return null;
		double erg=runden(zahl, 0);
		return (int)erg;
	}

	public static int runden2int(double zahl) {
		double erg=runden(zahl, 0);
		return (int)erg;
	}

	public static Integer runden2Integer(Double zahl) {
		if(zahl==null) return null;
		double erg=runden(zahl, 0);
		return (int)erg;
	}

	public static short runden2short(double zahl) {
		double erg=runden(zahl, 0);
		return (short)erg;
	}

	public static Short runden2Short(Double zahl) {
		if(zahl==null) return null;
		double erg=runden(zahl, 0);
		return (short)erg;
	}

	// DIVERSES

	/**
	 * Korrigiert das Problem mit den fraktalen Werten.
	 * Z.B. 3 * 1/3 = 0.9999999999999
	 */
	public static double normalisiere(double d, int dezimalen) {
		double m=Math.pow(10, dezimalen+1);
		return Math.rint(d*m)/m;
	}

	public static <T extends Number> T begrenzen(T min, T max, T zahl) {
		Fehler.zahl.wenn_ZuKlein(min.doubleValue(),max.doubleValue());
		return zahl.doubleValue()<min.doubleValue() ? min : zahl.doubleValue()>max.doubleValue() ? max : zahl;
	}

	public static double potenz(double zahl, double hoch) {
		return Math.pow(zahl, hoch);
	}

	public static boolean gerade(long zahl) {
		return zahl%2==0;
	}

	public static double minusProzent(double wert, double prozent) {
		Fehler.zahl.wenn_Ausserhalb(0, 100, prozent);
		return wert*((100-prozent)/100);
	}

	public static double diffProzent(double von, double zu) {
		return von<zu
			? zu/von*100-100
			: zu<von
				? -(100-(zu/von*100))
				: 0d;
	}

	public static int    absBetrag(int    wert)   { return Math.abs(wert); }
	public static long   absBetrag(long   wert)   { return Math.abs(wert); }
	public static float  absBetrag(float  wert)   { return Math.abs(wert); }
	public static double absBetrag(double wert)   { return Math.abs(wert); }

	public static int quersumme(long l) {
		int sum=0;
		l=Mathe.absBetrag(l);
		String sl=""+l;
		for(char c : sl.toCharArray())
			sum+=c-'0';
		return sum;
	}

	/**
	 * a^x=b  >>  x=log(a,b)
	 */
	public static double logX(double basis, double wert) {
		return Math.log(wert)/Math.log(basis);
	}

	public static int bitBenoetigt(long zahl) {
		Fehler.zahl.wenn_ZuKlein(0, zahl); //Vielleicht später mal!
		int erg=0;
		int bitgesamt=0;
		while(zahl>=bitgesamt) {
			if(bitgesamt==0) bitgesamt=1;
			bitgesamt*=2;
			erg++;
		}
		return erg;
	}

	public static Point schnitt(int start1, int laenge1, int start2, int laenge2) {
		int ende1=start1+laenge1;
		int ende2=start2+laenge2;
		if(ende1<start2 || ende2<start1)
			return null;
		int start=Math.max(start1, start2);
		int laenge=Math.min(ende1, ende2)-start;
		if(laenge==0)
			return null;
		return new Point(start, laenge);
	}

	public static int bitsSetzen(int wert, int bits) {
		return wert | bits;
	}

	public static int bitsLoeschen(int wert, int bits) {
		return (wert | bits) ^ bits;
	}

	public static boolean bitsGesetzt(int wert, int bits) {
		return (wert & bits) == bits;
	}

	public static int ganzzahl(double d) {
		return (int)Math.floor(d);
	}

	public static Integer minus(Integer minuend, Integer subtrahend) {
		if(minuend==null)
			return null;
		if(subtrahend==null)
			return minuend;
		return minuend-subtrahend;
	}

}
